package com.ruoyi.tiktok.service.impl;


import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.model.*;
import com.aliyuncs.exceptions.ClientException;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PreDestroy;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;


@Service
public class AliOssService {
    private static final Logger logger = LoggerFactory.getLogger(AliOssService.class);
    private OSS ossClient;
    private final String aliBucketAddress;

    private final String videoBucketName;

    public AliOssService(){
        // 从环境变量中获取访问凭证。运行本代码示例之前，请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        this.aliBucketAddress =  "https://oss-cn-nanjing.aliyuncs.com";
        final String storageAddress   =  "https://tuike-videos.oss-cn-nanjing.aliyuncs.com";
        this.videoBucketName  =  "tuike-videos";
        try {
            // Endpoint以华东1（杭州）为例，其它Region请按实际情况填写。
            // String endpoint = "https://oss-cn-nanjing.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前，请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider
                    = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 创建OSSClient实例。
            this.ossClient = new OSSClientBuilder().build(aliBucketAddress, credentialsProvider);
        } catch (ClientException e) {
            throw new RuntimeException(e);
        }

    }

    public void multiFileUpload(String endpoint, String bucketName,
                                String objectName,String downLoadName, MultipartFile multipartFile)
            throws IOException {
        logger.info("endpoint: "+ endpoint);
        logger.info("bucketName: "+ bucketName);
        logger.info("objectName: "+ objectName);
        String[] infos =  objectName.split("\\.");
        String fileType  = infos[infos.length-1];
        // 创建OSSClient实例。
        InputStream instream =null;
        try {
            // 创建InitiateMultipartUploadRequest对象。
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);

            // 如果需要在初始化分片时设置请求头，请参考以下示例代码。
            ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // 指定该Object的网页缓存行为。
            // metadata.setCacheControl("no-cache");

            // 指定该Object的内容编码格式。
            // metadata.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
            // 指定初始化分片上传时是否覆盖同名Object。此处设置为true，表示禁止覆盖同名Object。
            // metadata.setHeader("x-oss-forbid-overwrite", "true");
            // 指定上传该Object的每个part时使用的服务器端加密方式。
            // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
            // 指定Object的加密算法。如果未指定此选项，表明Object使用AES256加密算法。
            // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_DATA_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
            // 指定KMS托管的用户主密钥。
            // metadata.setHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, "9468da86-3509-4f8d-a61e-6eab1eac****");
            // 指定Object的存储类型。
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard);
            // 指定Object的对象标签，可同时设置多个标签。
            // metadata.setHeader(OSSHeaders.OSS_TAGGING, "a:1");

            // 指定该Object被下载时的名称。
            metadata.setHeader("Content-Disposition", "attachment; filename=" + downLoadName);
            if ("mp4".equals(fileType)) {
                metadata.setContentType("video/mp4");
            } else if ("mov".equals(fileType)) {
                metadata.setContentType("video/quicktime");
            }

            request.setObjectMetadata(metadata);
            // 初始化分片。
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            // 返回uploadId。
            String uploadId = upresult.getUploadId();
            // 根据uploadId执行取消分片上传事件或者列举已上传分片的操作。
            // 如果您需要根据uploadId执行取消分片上传事件的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后获取uploadId。
            // 如果您需要根据uploadId执行列举已上传分片的操作，您需要在调用InitiateMultipartUpload完成初始化分片之后，且在调用CompleteMultipartUpload完成分片上传之前获取uploadId。
            // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
            List<PartETag> partETags =  new ArrayList<>();
            // 每个分片的大小，用于计算文件有多少个分片。单位为字节。
            final long partSize = 1 * 1024 * 1024L;   //1 MB。

            // 根据上传的数据大小计算分片数。以本地文件为例，说明如何通过File.length()获取上传数据的大小。
            // final File sampleFile = new File(filePath);
            long fileLength = multipartFile.getSize();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }

            // 遍历分片上传。
            for (int i = 0; i < partCount; i++) {
                long startPos = i * partSize;
                long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                uploadPartRequest.setBucketName(bucketName);
                uploadPartRequest.setKey(objectName);
                uploadPartRequest.setUploadId(uploadId);

                instream = multipartFile.getInputStream();

                instream.skip(startPos);
                uploadPartRequest.setInputStream(instream);
                // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100 KB。
                uploadPartRequest.setPartSize(curPartSize);
                // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出此范围，OSS将返回InvalidArgument错误码。
                uploadPartRequest.setPartNumber( i + 1);
                // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。

               UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
               logger.info("Part " + (i + 1) + " uploaded with ETag: " + uploadPartResult.getResponse().getStatusCode());
               logger.info("Part " + (i + 1) + " uploaded with ETag: " + uploadPartResult.getPartETag().getETag());

               // 每次上传分片之后，OSS的返回结果包含PartETag。PartETag将被保存在partETags中。
               partETags.add(uploadPartResult.getPartETag());
            }


            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);

            // 如果需要在完成分片上传的同时设置文件访问权限，请参考以下示例代码。
            // completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.Private);
            // 指定是否列举当前UploadId已上传的所有Part。仅在Java SDK为3.14.0及以上版本时，支持通过服务端List分片数据来合并完整文件时，将CompleteMultipartUploadRequest中的partETags设置为null。
            // Map<String, String> headers = new HashMap<String, String>();
            // 如果指定了x-oss-complete-all:yes，则OSS会列举当前UploadId已上传的所有Part，然后按照PartNumber的序号排序并执行CompleteMultipartUpload操作。
            // 如果指定了x-oss-complete-all:yes，则不允许继续指定body，否则报错。
            // headers.put("x-oss-complete-all","yes");
            // completeMultipartUploadRequest.setHeaders(headers);

            // 完成分片上传。
            CompleteMultipartUploadResult completeMultipartUploadResult =
                    ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            logger.info(completeMultipartUploadResult.getRequestId());


        } catch (OSSException oe) {
            logger.error("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            logger.error("Error Message:" + oe.getErrorMessage());
            logger.error("Error Code:" + oe.getErrorCode());
            logger.error("Request ID:" + oe.getRequestId());
            logger.error("Host ID:" + oe.getHostId());
        }  catch (IOException e) {
            logger.error(e.getMessage());
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //if (ossClient != null) {
            //    ossClient.shutdown();
            //}
            if (instream != null) {
                instream.close();
            }
        }
    }


  //@Async   // 异步生成封面图
    public void generateCoverImage(MultipartFile multipartFile,  String imgFileName) throws IOException {
        logger.info("文件上传完成，开始抽取视频封面....");
        byte[] imageBytes = extractFrame(multipartFile.getInputStream(),1);
        if(imageBytes != null){
            uploadToOSS(imgFileName, videoBucketName, imageBytes);
            logger.info("upload cover image finish" + imgFileName );
        }
    }
    public byte[] extractFrame(InputStream inputStream, double timestampInSeconds){
        FFmpegFrameGrabber frameGrabber = null;
        byte[] imageBytes = null;
        try {
            frameGrabber = new FFmpegFrameGrabber(inputStream);

            frameGrabber.start();
            // 将时间转换为帧位置
            int frameNumber = (int) (timestampInSeconds * frameGrabber.getFrameRate());
            frameGrabber.setFrameNumber(frameNumber);

            // 获取指定帧
            Frame frame = frameGrabber.grabImage();
            if (frame != null) {
                // 转换为 BufferedImage
                Java2DFrameConverter converter = new Java2DFrameConverter();
                BufferedImage bufferedImage = converter.convert(frame);


                // 调整图像的分辨率.给定义一个图片的大小
                int targetWidth =300;
                int targetHeight = 500;
                BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
                Graphics2D g2d = resizedImage.createGraphics();
                g2d.drawImage(bufferedImage, 0, 0, targetWidth, targetHeight, null);
                g2d.dispose();

                // 将压缩后的图像保存为 JPEG 并控制压缩质量
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
                ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
                writer.setOutput(ios);

                ImageWriteParam jpegParams = writer.getDefaultWriteParam();
                jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                jpegParams.setCompressionQuality(0.6f); // 调整 JPEG 压缩质量 (0.0 - 1.0)，0.7 表示压缩质量为 70%

                writer.write(null, new javax.imageio.IIOImage(resizedImage, null, null), jpegParams);

                imageBytes = baos.toByteArray();

                ios.close();
                writer.dispose();

                // 保存图像为 PNG
                // 将 BufferedImage 转换为字节数组
                // ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // ImageIO.write(bufferedImage, "png", baos);
                // imageBytes = baos.toByteArray();
                //ImageIO.write(bufferedImage, "png", new File(outputImagePath));
                //logger.info("Frame extracted successfully to " + outputImagePath);
            } else {
                logger.info("No frame captured at the given timestamp.");
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        } finally {
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (frameGrabber != null) {
                try {
                    frameGrabber.stop();
                } catch (FFmpegFrameGrabber.Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return imageBytes;
    }

    private  PutObjectResult uploadToOSS(String objectName, String bucketName ,byte[] data) {
        // 创建上传请求
        PutObjectRequest putRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(data));
        // 上传文件
       return this.ossClient.putObject(putRequest);
    }
    @PreDestroy
    public void shutdown() {
        // 在服务销毁时关闭 OSS Client
        if (ossClient != null) {
            ossClient.shutdown();
        }
    }
}
